/**
 * Module dependencies
 */

var util = require("util");
var _ = require("@sailshq/lodash");
var flaverr = require("flaverr");
var async = require("async");
var prompt = require("prompt");
var chalk = require("chalk");
var loadModelsAndCustomAdapters = require("./load-models-and-custom-adapters");
var validateDatastoreConfig = require("./validate-datastore-config");
var validateModelDef = require("./validate-model-def");
var buildOntologyAndRunAutoMigrations = require("./build-ontology-and-run-auto-migrations");
var loadAdapterFromAppDependencies = require("./load-adapter-from-app-dependencies");
var buildRegisteredDatastoreInstance = require("./build-registered-datastore-instance");
var checkAdapterCompatibility = require("./check-adapter-compatibility");

/**
 * `initialize()`
 *
 * Initialize this hook.
 *
 * @required  {Dictionary} hook
 * @required  {SailsApp} sails
 * @required  {Function} done
 */
module.exports = function initialize(hook, sails, done) {
  ////////////////////////////////////////////////////////////////////////////
  // NOTE: If a user hook needs to add or modify model definitions,
  // the hook should wait until `hook:orm:loaded`, then reload the original
  // model modules `orm/loadAppModules`. Finally, the ORM should be flushed using
  // `reload()` below.
  ////////////////////////////////////////////////////////////////////////////

  // Now do a number of things, some of them in parallel.
  async.auto(
    {
      // Load model and adapter definitions which are defined in the project.
      // (this is from e.g. `api/models/` and `api/adapters/`--
      //  note that this does NOT include adapters which need to be loaded from the node_modules directory!)
      _loadModelsAndCustomAdapters: function (next) {
        loadModelsAndCustomAdapters(hook, sails, next);
      },

      // Warning!  This feature is undocumented/experimental and may change at any time!
      _mergeInProgrammaticModuleDefs: [
        "_loadModelsAndCustomAdapters",
        function (unused, next) {
          _.extend(hook.models, sails.config.orm.moduleDefinitions.models);
          _.extend(hook.adapters, sails.config.orm.moduleDefinitions.adapters);
          return next();
        },
      ],

      // Get an array of datastore identities.
      //
      // Note that we do not attempt to validate/normalize model defs or datastore configs here.
      // If model defs or datastore configs cannot be parsed, we simply ignore them.
      _determineRelevantDatastoreNames: [
        "_mergeInProgrammaticModuleDefs",
        function (unused, next) {
          var relevantDatastoreNames = [];

          try {
            _.each(
              sails.config.datastores,
              function _eachDatastoreConfig(datastoreConfig, datastoreName) {
                // If the datastore config is even remotely valid, then fail with a fatal error.
                // (note that this will also be more thoroughly checked later)
                if (
                  !_.isObject(datastoreConfig) ||
                  (!_.isString(datastoreConfig.adapter) &&
                    !_.isObject(datastoreConfig.adapter))
                ) {
                  throw flaverr(
                    { name: "userError", code: "E_INVALID_DATASTORE_CONFIG" },
                    new Error(
                      "Invalid configuration for datastore `" +
                        datastoreName +
                        "`:\n" +
                        util.inspect(datastoreConfig, { depth: 5 }) +
                        ""
                    )
                  );
                }

                // Now, if we made it here, then we know this datastore is relevant.
                relevantDatastoreNames.push(datastoreName);
              }
            ); //</_.each>
          } catch (e) {
            return next(e);
          }

          // Always put "default" last.  This way when loading adapters, we can have it use
          // the 'sails-disk' adapter declared in another datastore (if any) instead of loading
          // the default one.
          relevantDatastoreNames = _.without(
            relevantDatastoreNames,
            "default"
          ).concat(["default"]);

          return next(undefined, relevantDatastoreNames);
        },
      ],

      //  ╦  ╔═╗╔═╗╔╦╗  ╔═╗╔╦╗╔═╗╔═╗╔╦╗╔═╗╦═╗┌─┐
      //  ║  ║ ║╠═╣ ║║  ╠═╣ ║║╠═╣╠═╝ ║ ║╣ ╠╦╝└─┐
      //  ╩═╝╚═╝╩ ╩═╩╝  ╩ ╩═╩╝╩ ╩╩   ╩ ╚═╝╩╚═└─┘
      //  ┌─  ┌─┐┬─┐┌─┐┌┬┐  ┌─┐┌─┐┌─┐  ┌┐┌┌─┐┌┬┐┌─┐    ┌┬┐┌─┐┌┬┐┬ ┬┬  ┌─┐┌─┐  ┌─┐┌─┐┬  ┌┬┐┌─┐┬─┐  ─┐
      //  │───├┤ ├┬┘│ ││││  ├─┤├─┘├─┘  ││││ │ ││├┤     ││││ │ │││ ││  ├┤ └─┐  ├┤ │ ││   ││├┤ ├┬┘───│
      //  └─  └  ┴└─└─┘┴ ┴  ┴ ┴┴  ┴    ┘└┘└─┘─┴┘└─┘────┴ ┴└─┘─┴┘└─┘┴─┘└─┘└─┘  └  └─┘┴─┘─┴┘└─┘┴└─  ─┘
      //
      // For every valid datastore config which is relevant (i.e. referenced by at least one model, or app-wide defaults, or with `forceLoadAdapter` set)
      // ensure its referenced adapter is loaded. Note that we do not attempt to validate/normalize stuff here-- the goal is just to ensure we
      // have the referenced adapters.
      //
      // If we find a not-yet-loaded adapter being referenced from an in-use datastore, then attempt to require it from the `node_modules/`
      // directory of this Sails application.
      _attemptToLoadUnrecognizedAdapters: [
        "_determineRelevantDatastoreNames",
        function (aaData, next) {
          try {
            _.each(
              aaData._determineRelevantDatastoreNames,
              function _eachRelevantDatastoreName(datastoreName) {
                // Now, if we made it here, then we're ready to take a look at this datastore config and check up on its adapter.
                var datastoreConfig = sails.config.datastores[datastoreName];

                // Check if the referenced adapter has aready been loaded one way or another.
                if (
                  _.isObject(datastoreConfig) &&
                  _.isString(datastoreConfig.adapter)
                ) {
                  var referencedAdapter =
                    hook.adapters[datastoreConfig.adapter];

                  // If it hasn't...
                  if (!referencedAdapter) {
                    try {
                      // Otherwise, we'll try and load it as a dependency from the app's `node_modules/` folder,
                      // and also validate and normalize it.
                      hook.adapters[
                        datastoreConfig.adapter
                      ] = loadAdapterFromAppDependencies(
                        datastoreConfig.adapter,
                        datastoreName,
                        sails
                      );
                    } catch (e) {
                      // Special case -- the default adapter will load the sails-disk bundled with sails-hook-orm if it's not installed locally.
                      if (
                        datastoreName === "default" &&
                        datastoreConfig.adapter === "sails-disk" &&
                        e.code === "E_ADAPTER_NOT_INSTALLED"
                      ) {
                        hook.adapters[
                          datastoreConfig.adapter
                        ] = require("sails-disk");
                      } else {
                        throw e;
                      }
                    }
                  } //</ if the adapter string references an adapter that we haven't loaded yet >-
                } //</ if datastore config is an object and its `adapter` prop is a string >-
              }
            ); //</_.each() :: each relevant datastore identity>
          } catch (e) {
            return next(e);
          }

          return next();
        },
      ],

      // Validate and normalize datastore configurations.
      _validateDatastoreConfigsAndBuildAccessors: [
        "_attemptToLoadUnrecognizedAdapters",
        function (aaData, next) {
          try {
            // Loop over relevant datastore configs and validate/normalize the raw config of each one,
            // saving the normalized config on `hook.normalizedDSConfigs` for use in subsequent steps.
            hook.normalizedDSConfigs = {};
            _.each(
              aaData._determineRelevantDatastoreNames,
              function _eachRelevantDatastoreName(datastoreName) {
                hook.normalizedDSConfigs[
                  datastoreName
                ] = validateDatastoreConfig(datastoreName, hook, sails);
              }
            ); //</_.each() datastore name>
          } catch (e) {
            return next(e);
          }

          return next();
        },
      ],

      _checkForGlobalMongoSettings: [
        "_validateDatastoreConfigsAndBuildAccessors",
        function (aaData, next) {
          try {
            // Get the default adapter.
            var defaultAdapterIdentity =
              hook.normalizedDSConfigs.default.adapter;

            // Get the default primary key attribute name.
            var defaultPrimaryKey = sails.config.models.primaryKey;

            // Get the default primary key attribute def (if any)
            var defaultPrimaryKeyAttr =
              sails.config.models.attributes[defaultPrimaryKey];

            // If no attribute exists, we're done (for now).  Later on, each model will be checked
            // individually to ensure that if it's using Mongo, it has a correctly-configured PK attribute.
            // So no reason to get our panties all in a bunch about it just yet.
            if (!defaultPrimaryKeyAttr) {
              throw flaverr(
                "E_NO_ATTR_DEF_FOR_DEFAULT_PKA",
                new Error(
                  "This model does not have an attribute for the default primary key.  That does not mean it is necessarily broken (it might have a custom PKA -- we just haven't bothered to look into it further yet, since we'll be checking again momentarily anyway."
                )
              );
            }

            if (
              defaultAdapterIdentity === "sails-mongo" &&
              (defaultPrimaryKeyAttr.autoIncrement ||
                (defaultPrimaryKeyAttr.type !== "string" &&
                  sails.config.models.dontUseObjectIds !== true) ||
                defaultPrimaryKeyAttr.columnName !== "_id")
            ) {
              sails.log.debug(
                "It looks like the default datastore for this app is `sails-mongo`,"
              );
              sails.log.debug(
                "but the default primary key attribute (`" +
                  defaultPrimaryKey +
                  "`) is not set up correctly."
              );
              sails.log.debug(
                "When using `sails-mongo`, primary keys MUST have `columnName: '_id'`,"
              );
              sails.log.debug("and must _not_ have `autoIncrement: true`.");
              sails.log.debug(
                "Also, if `dontUseObjectIds` is not set to `true` for the model,"
              );
              sails.log.debug(
                "then the `type` of the primary key must be `string`."
              );
              sails.log.debug();
              sails.log.debug("We'll set this up for you this time...");
              sails.log.debug();
              delete defaultPrimaryKeyAttr.autoIncrement;
              defaultPrimaryKeyAttr.type = "string";
              defaultPrimaryKeyAttr.columnName = "_id";
            }
          } catch (e) {
            switch (e.code) {
              case "E_NO_ATTR_DEF_FOR_DEFAULT_PKA":
                return next();
              default:
                return next(e);
            }
          } //>-•

          return next();
        },
      ],

      // Normalize model definitions and merge in defaults from `sails.config.models.*`.
      // This is what will be passed in to Waterline when building the ontology.
      _normalizeModelDefs: [
        "_validateDatastoreConfigsAndBuildAccessors",
        function (unused, next) {
          try {
            _.each(_.keys(hook.models), function (identity) {
              var normalizedModelDef = validateModelDef(
                hook.models[identity],
                identity,
                hook,
                sails
              );
              // Note: prior to March 2016, the normalized def was merged back into
              // the original model def (`hook.models[identity]`) rather than replacing it.
              hook.models[identity] = normalizedModelDef;

              // Ensure that the identity that is set in sails.hook.models is equal to the
              // model's identity so that you never end up with a global model id that is
              // different from the model identity.
              if (identity.toLowerCase() !== normalizedModelDef.identity) {
                throw new Error(
                  "A model was found that has different values for it's globalId and it's model identity. You should never manually set these values, they will be inferred for you."
                );
              }
            });
          } catch (e) {
            return next(e);
          }

          return next();
        },
      ],

      //  ╔═╗╦═╗╔═╗╔╦╗╦ ╦╔═╗╔╦╗╦╔═╗╔╗╔  ╔═╗╦ ╦╔═╗╔═╗╦╔═
      //  ╠═╝╠╦╝║ ║ ║║║ ║║   ║ ║║ ║║║║  ║  ╠═╣║╣ ║  ╠╩╗
      //  ╩  ╩╚═╚═╝═╩╝╚═╝╚═╝ ╩ ╩╚═╝╝╚╝  ╚═╝╩ ╩╚═╝╚═╝╩ ╩
      //  ┌─  ┬ ┬┌─┐┬─┐┌┐┌┬┌┐┌┌─┐┌─┐   ─┐
      //  │───│││├─┤├┬┘││││││││ ┬└─┐ ───│
      //  └─  └┴┘┴ ┴┴└─┘└┘┴┘└┘└─┘└─┘   ─┘
      //
      // If NODE_ENV is "production", check if any models are using
      // a datastore running on `sails-disk`.  If so, show a warning.
      _productionCheck: [
        "_normalizeModelDefs",
        function (unused, next) {
          try {
            // We use `process.env.NODE_ENV` instead of `sails.config.environment`
            // to allow for the environment to be set to e.g. "staging" while the
            // NODE_ENV is set to "production".
            if (process.env.NODE_ENV === "production") {
              // > **Remember:**
              // > In a production environment, regardless of your logical `environment`
              // > config, the NODE_ENV environment variable should be set.  Setting
              // > `sails.config.environment` to production does this automatically.

              // e.g. ['default', 'foobar']
              var datastoresUsingSailsDisk = _.reduce(
                sails.config.datastores,
                function (memo, datastoreConf, identity) {
                  if (datastoreConf.adapter === "sails-disk") {
                    memo.push(identity);
                  }
                  return memo;
                },
                []
              );

              // e.g. ['user', 'product']
              var modelsUsingSailsDisk = _.reduce(
                hook.models,
                function (memo, normalizedModelDef, identity) {
                  // Look up the referenced datastore for this model, and then check to see if
                  // it matches any of the datastores using the sails-disk adapter.
                  var referencedDatastore = normalizedModelDef.datastore;
                  if (
                    _.contains(datastoresUsingSailsDisk, referencedDatastore)
                  ) {
                    memo.push(identity);
                  }
                  return memo;
                },
                []
              );

              if (modelsUsingSailsDisk.length > 0) {
                sails.log.warn(
                  "The default `sails-disk` adapter is not designed for use as a production database."
                );
                sails.log.warn(
                  "Instead, please use another adapter like sails-postgresql or sails-mongo."
                );
                sails.log.warn(
                  "For more info, see: http://sailsjs.com/docs/concepts/deployment"
                );
                sails.log.warn(
                  "To hide this warning message, enable `sails.config.orm.skipProductionWarnings`."
                );
                sails.log.warn(
                  " [?] If you're unsure, see https://sailsjs.com/support"
                );
              }
            } //>-
          } catch (e) {
            return next(e);
          }

          // Otherwise it worked!
          return next();
        },
      ],

      // Before continuing any further to actually start up the ORM,
      // check the migrate settings for each model to prompt the user
      // to make a decision if no migrate configuration is present.
      //
      // Note that, if this is a production environment, the `migrate`
      // setting will always be forced to "safe" in Waterline.
      _doubleCheckMigration: [
        "_productionCheck",
        function (unused, next) {
          // If there are no models, we're good.
          if (_.keys(hook.models).length === 0) {
            return next();
          }

          // If a project-wide migrate setting (sails.config.models.migrate) is defined, we're good.
          if (!_.isUndefined(sails.config.models.migrate)) {
            return next();
          }

          // If this is a production NODE_ENV, show a slightly different message and skip the prompt.
          if (process.env.NODE_ENV === "production") {
            console.log("");
            sails.log.info(
              "A project-wide `sails.config.models.migrate` setting has not been configured for this app."
            );
            sails.log.info(
              'Since the NODE_ENV env variable is set to "production", auto-migration will be disabled automatically.'
            );
            sails.log.info("(i.e. `migrate: 'safe'`)");
            return next();
          }

          // Otherwise show a prompt
          console.log(
            "--------------------------------------------------------------------------------"
          );
          // console.log();
          prompt.start();
          console.log(
            " Excuse my interruption, but it looks like this app\n" +
              ' does not have a "migrate" setting configured yet.\n' +
              " (perhaps this is the first time you're lifting it with models?)\n" +
              ""
            // '\n'+
            // ' In short, this setting controls whether/how Sails attempts to\n'+
            // ' automatically rebuild your database(s) every time you lift.\n'+
            // ' You can read more about that here:\n'+
            // ' '+chalk.underline('sailsjs.com/docs/concepts/models-and-orm/model-settings#?migrate')+'\n'
            // // ' command(⌘)+click to open links in the terminal'
          );
          // console.log();
          console.log(
            chalk.gray(
              " Tired of seeing this prompt?  Edit " +
                chalk.bold("config/models.js") +
                "."
            )
          );
          // console.log(chalk.gray(' Or for a one-time override: '+chalk.bold('sails lift --models.migrate=\'alter\'')+''));
          // console.log();
          // console.log(chalk.gray(
          //   ' In short, this setting controls whether/how Sails attempts to\n'+
          //   ' automatically rebuild your database(s) every time you lift.\n'+
          //   ' You can read more about that here:\n'+
          //   ' '+chalk.underline('sailsjs.com/docs/concepts/models-and-orm/model-settings#?migrate')
          //   // ' command(⌘)+click to open links in the terminal'
          // ));
          console.log();
          console.log(
            " In a production environment (NODE_ENV=production) Sails always uses" +
              "\n" +
              " migrate:'safe' to protect against inadvertent deletion of your data.\n" +
              " But " +
              chalk.bold("during development") +
              ", you have a few different options:\n" +
              "\n" +
              " 1. FOR DEV:      " +
              chalk.bold.cyan("alter") +
              chalk.reset("   wipe/drop and try to re-insert ALL my data ") +
              chalk.bold("(recommended)") +
              "\n" +
              " 2. FOR TESTS:    " +
              chalk.bold.yellow("drop") +
              chalk.reset(
                "    wipe/drop ALL my data every time I lift Sails\n"
              ) +
              " 3. FOR STAGING:  " +
              chalk.bold.red("safe") +
              chalk.reset(
                "    don't auto-migrate my data. I will do it myself\n"
              )
          );
          // console.log('What would you like Sails to do '+chalk.bold('this time')+'?');
          console.log(
            chalk.gray(
              " Read more: " +
                chalk.underline(
                  "sailsjs.com/docs/concepts/models-and-orm/model-settings#?migrate"
                )
            )
          );
          console.log(
            "--------------------------------------------------------------------------------"
          );
          console.log();
          console.log(
            "What would you like Sails to do " + chalk.bold("this time") + "?"
          );
          console.log(
            chalk.gray(
              ' ** NEVER CHOOSE "alter" or "drop" IF YOU ARE WORKING WITH PRODUCTION DATA **'
            )
          );
          console.log();
          // console.log();
          prompt.get(["?"], function (err, result) {
            if (err) {
              console.log("_.keys(err)", _.keys(err));
              console.log(
                chalk.bgRed.white.bold("<canceled, probably with CTRL+C>")
              );
              console.log(
                "--------------------------------------------------------------------------------"
              );
              return next(err);
            } //-•

            result = result["?"];

            switch (result) {
              case "alter":
              case "1":
                sails.config.models.migrate = "alter";
                break;
              case "drop":
              case "2":
                sails.config.models.migrate = "drop";
                break;
              default:
                sails.config.models.migrate = "safe";
                break;
            }

            console.log(
              "--------------------------------------------------------------------------------"
            );
            // console.log();
            console.log(
              " OK!  Temporarily using " +
                chalk.bold("migrate:'" + sails.config.models.migrate + "'") +
                "..."
            );
            // console.log(' (press CTRL+C to cancel-- continuing automatically in 0.5 seconds...)');
            console.log(
              chalk.gray(
                " To skip this prompt in the future, edit " +
                  chalk.bold("config/models.js") +
                  "."
              )
            );
            console.log(
              "--------------------------------------------------------------------------------"
            );
            console.log();
            setTimeout(function () {
              return next();
            }, 300);
          });
        },
      ],

      // Verify that this adapter is compatible w/ this version of Sails / Waterline.
      // (if not, go ahead and throw)
      _checkAdapterCompatibility: [
        "_doubleCheckMigration",
        function (aaData, next) {
          _.each(
            aaData._determineRelevantDatastoreNames,
            function _eachRelevantDatastoreName(datastoreName) {
              // Look up the normalized config for this datastore.
              var normalizedDatastoreConfig =
                hook.normalizedDSConfigs[datastoreName];

              // Find the adapter used by this datastore
              var adapter = hook.adapters[normalizedDatastoreConfig.adapter];

              checkAdapterCompatibility(datastoreName, adapter);
            }
          );

          return next();
        },
      ],

      // Once all user model and adapter definitions are loaded
      // and normalized, go ahead and initialize the ORM.
      _buildOntology: [
        "_checkAdapterCompatibility",
        function (aaData, next) {
          // If `sails` is already exiting due to previous errors, bail out.
          if (sails._exiting) {
            // This is possible since we are doing asynchronous things in the initialize function,
            // and e.g. another hook may have failed to load in the mean time since we began initializing.
            // Also note that `reload()` below calls initialize again, so this could happen during that
            // process as well.
            return next(
              flaverr("E_SAILS_IS_ALREADY_EXITING", new Error("SAILS EXITING"))
            );
          }

          if (sails.config.models.migrate === "alter") {
            sails.log.info(
              chalk.cyan.bold("·• ") +
                chalk.bold("Auto-migrating...") +
                chalk.reset("  (alter)")
            );
            sails.log.info(
              "   " + chalk.gray("Hold tight, this could take a moment.")
            );
            // sails.log.info('   '+chalk.gray('(Please don\'t press CTRL+C until this is finished.)'));
          } else if (sails.config.models.migrate === "drop") {
            sails.log.info(
              chalk.yellow.bold("·• ") +
                chalk.bold("Auto-migrating...") +
                chalk.reset("  (drop)")
            );
            // sails.log.info('   '+chalk.gray('(Please don\'t press CTRL+C until this is finished.)'));
          } else {
          }

          buildOntologyAndRunAutoMigrations(
            hook,
            sails,
            function (err, freshOntology) {
              if (err) {
                return next(err);
              }

              if (sails.config.models.migrate === "alter") {
                sails.log.info(" ✓ Auto-migration complete.");
                sails.log.blank();
              } else if (sails.config.models.migrate === "drop") {
                sails.log.info(" ✓ Auto-migration complete.");
                sails.log.blank();
              }

              // Finally, continue onward, passing the ontology through for use below.
              return next(undefined, freshOntology);
            }
          );
        },
      ],

      // Expose `hook.datastores`; a dictionary indexed by datastore identity.
      // We only build datastore dictionaries for datastore configs that are in use by one or more models
      // (or have the `forceLoadAdapter` setting enabled).
      _buildRDIs: [
        "_buildOntology",
        function (aaData, next) {
          try {
            // Start building `hook.datastores`
            hook.datastores = {};

            // Loop over relevant datastore configs.
            _.each(
              aaData._determineRelevantDatastoreNames,
              function _eachRelevantDatastoreName(datastoreName) {
                // Look up the normalized config for this datastore.
                var normalizedDatastoreConfig =
                  hook.normalizedDSConfigs[datastoreName];

                // Find the adapter used by this datastore
                var adapter = hook.adapters[normalizedDatastoreConfig.adapter];

                // Build our registered datastore instance (s.k.a. "rdi") - a dictionary
                // with public methods as well as other important state.  Then expose
                // our new rdi on `hook.datastores`.
                hook.datastores[
                  datastoreName
                ] = buildRegisteredDatastoreInstance(
                  datastoreName,
                  normalizedDatastoreConfig,
                  adapter
                );
              }
            ); //</_.each() datastore name>
          } catch (e) {
            return next(e);
          }

          //  ╔╦╗╔═╗╔╦╗╔═╗╔═╗╔╦╗╔═╗╦═╗╔═╗  ┌─┐┌─┐┌┬┐┌┬┐┌─┐┬─┐
          //   ║║╠═╣ ║ ╠═╣╚═╗ ║ ║ ║╠╦╝║╣   │ ┬├┤  │  │ ├┤ ├┬┘
          //  ═╩╝╩ ╩ ╩ ╩ ╩╚═╝ ╩ ╚═╝╩╚═╚═╝  └─┘└─┘ ┴  ┴ └─┘┴└─
          // Attach a getter function to the hook for returning the correct datastore
          // by name.

          /**
           * @param  {String?} datastoreName
           *         defaults to "default"
           *
           * @returns {Dictionary}
           *          The registered datastore instance (RDI)
           */
          hook.getDatastore = function getDatastore(datastoreName) {
            // If no datastore name was specified, then assume the "default" datastore.
            if (_.isUndefined(datastoreName)) {
              datastoreName = "default";
            } //>-

            var foundDatastore = hook.datastores[datastoreName];
            if (!foundDatastore) {
              throw new Error(
                "Could not find a datastore by that name. Perhaps it hasn't been defined in the configuration?"
              );
            } //-•

            // Return the registered datastore instance (RDI).
            // This includes public methods, plus state like `manager`, as well as static
            // metadata such as `config`, `driver`, and `name`.
            return foundDatastore;
          };

          // Also expose this as sails.getDatastore().
          sails.getDatastore = hook.getDatastore;

          // And finally, go ahead and grab a (singleton) reference to the
          // default datastore, and use it to expose a couple of the most
          // common datastore methods directly on the `sails` instance,
          // purely for convenience.
          // (These methods will always use the default datastore.)
          var defaultRDISingleton = hook.getDatastore();

          sails.sendNativeQuery = defaultRDISingleton.sendNativeQuery;
          sails.transaction = defaultRDISingleton.transaction;

          // All done w/ this step.
          return next();
        },
      ],

      // Now take each of the "collection" instances returned by Waterline and modify them a bit for Sails.
      // Then stuff them back onto `hook.models`.
      _augmentAndExposeFinalModels: [
        "_buildRDIs",
        function (aaData, next) {
          try {
            _.each(
              aaData._buildOntology.collections,
              function _eachInstantiatedModel(wlModel, modelIdentity) {
                // Bind context (`this`) for models.
                // (this allows `this` to be used in custom model methods)
                // Skip `customToJSON` since `this` inside a record's `toJSON` should
                // always refer to the record!
                _.each(wlModel, function (prop, propName) {
                  if (_.isFunction(prop) && propName !== "customToJSON") {
                    _.bind(prop, wlModel);
                  }
                });

                // Derive information about this model's associations from its schema
                // and attach/expose the metadata as `SomeModel.associations` (an array)
                wlModel.associations = _.reduce(
                  wlModel.attributes,
                  function _eachAttribute(memo, attrDef, attrName) {
                    // Skip non-associations.
                    if (
                      !_.isObject(attrDef) ||
                      (!attrDef.model && !attrDef.collection)
                    ) {
                      return memo;
                    }

                    // Build an informational dictionary describing this association.
                    var assocInfo = { alias: attrName };
                    if (attrDef.model) {
                      assocInfo.type = "model";
                      assocInfo.model = attrDef.model;
                    } else if (attrDef.collection) {
                      assocInfo.type = "collection";
                      assocInfo.collection = attrDef.collection;
                      if (attrDef.via) {
                        assocInfo.via = attrDef.via;
                      }
                    }
                    memo.push(assocInfo);
                    return memo;
                  },
                  []
                );

                // Added by Gaitho
                // For Instantiating a wlModel

                function modelInstance(original) {
                  var copied = Object.assign(
                    Object.create(Object.getPrototypeOf(original)),
                    original
                  );
                  return copied;
                }

                // Set `hook.models.*` reference to our instantiated model.
                // Exposed as `hook.models[modelIdentity]`.
                hook.models[modelIdentity] = wlModel;

                hook.models[`_${modelIdentity}`] = (ds) => {
                  const dsName = ds || "default";
                  const wlModelInstance = modelInstance(wlModel);
                  wlModelInstance.datastore = dsName;
                  return wlModelInstance;
                };

                // If configured to do so (based on `sails.config.globals.models`), then expose a reference
                // to this model as a global variable (based on its `globalId`).

                if (
                  _.isObject(sails.config.globals) &&
                  sails.config.globals.models === true
                ) {
                  // If this is an internal model introduced by Waterline (indicated by the `_private` flag)
                  // and not intended for userland access/manipulation, then don't globalize it.
                  // (e.g. this flag is attached to implicit junction models.)

                  if (hook.models[modelIdentity]._private) {
                    // Don't globalize.
                  }
                  // Otherwise use the `globalId` to determine what to globalize it as.
                  else if (
                    _.isString(hook.models[modelIdentity].globalId) &&
                    hook.models[modelIdentity].globalId !== ""
                  ) {
                    // Edited by Gaitho

                    //

                    var gId = hook.models[modelIdentity].globalId;

                    global[gId] = wlModel;

                    global[`_${gId}`] = (ds) => {
                      const dsName = ds || "default";
                      const wlModelInstance = modelInstance(wlModel);
                      wlModelInstance.datastore = dsName;
                      return wlModelInstance;
                    };
                  }
                  // If there is no `globalId`, fall back to the identity.
                  else {
                    global[modelIdentity] = wlModel;

                    // EDIT
                    global[`_${modelIdentity}`] = (ds) => {
                      const dsName = ds || "default";
                      const wlModelInstance = modelInstance(wlModel);
                      wlModelInstance.datastore = dsName;
                      return wlModelInstance;
                    };
                  }
                }
              }
            ); //</each collection from Waterline>
          } catch (e) {
            return next(e);
          }

          return next();
        },
      ],
    },
    done
  ); //</async.auto>
};
